package font;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import main.ErrorException;
import xml.XmlDoc;
import xml.XmlNode;
import define.Config;
import define.ErrorDefine;

public class Atlas
{
	protected ArrayList<RegionAble> m_blocks;
	protected ArrayList<RegionAble> m_emptyRegions;
	protected int m_size;
	protected ArrayList<BufferedImage> m_images;
	protected ArrayList<Integer> m_pages;
	
	public Atlas(int size)
	{
		m_size = size;
		resetRegion();
		m_images = new ArrayList<BufferedImage>();
		m_blocks = new ArrayList<RegionAble>();
	}
	
	public void addBlock(RegionAble block)
	{
		m_blocks.add(block);
	}
	
	protected int findRegion(int w, int h)
	{
		for(int i = 0, size = m_emptyRegions.size(); i < size; ++i)
		{
			RegionAble r = m_emptyRegions.get(i);
			if(r.getHeight() >= h && r.getWidth() >= w)
			{
				return i;
			}
		}
		return -1;
	}
	
	protected void resetRegion()
	{
		m_emptyRegions = new ArrayList<RegionAble>();
		m_emptyRegions.add(new Region(0, m_size - 1, 0, m_size - 1));
	}
	
	protected void sortRegion(ArrayList<RegionAble> list, int start, int end, boolean b2s)
	{
		if(start >= end)
		{
			return;
		}
		if(start == end - 1)
		{
			if(isAreaBig(list.get(end), list.get(start), b2s))
			{
				switchList(list, start, end);
			}
			return;
		}
		int i = start, j = end;
		RegionAble key = list.get(start);
		while(true)
		{
			RegionAble r = list.get(j);
			while (j > i && isAreaBig(key, r, b2s))
			{
				--j;
				r = list.get(j);
			}
			if(i == j)
			{
				break;
			}
			switchList(list, i, j);
			++i;
			r = list.get(i);
			while(i < j && isAreaBig(r, key, b2s))
			{
				++i;
				r = list.get(i);
			}
			if(i == j)
			{
				break;
			}
			switchList(list, i, j);
			--j;
		}
		sortRegion(list, 0, i - 1, b2s);
		sortRegion(list, i + 1, end, b2s);
	}
	
	private void switchList(ArrayList<RegionAble> list, int i1, int i2)
	{
		RegionAble temp = list.get(i1);
		list.set(i1, list.get(i2));
		list.set(i2, temp);
	}
	
	private boolean isAreaBig(RegionAble r1, RegionAble r2, boolean b2s)
	{
		int a1 = r1.getArea();
		int a2 = r2.getArea();
		if(a1 > a2)
		{
			return b2s? true: false;
		}
		if(a1 == a2)
		{
			return b2s? r1.getHeight() > r2.getHeight(): r1.getHeight() < r2.getHeight();
		}
		return b2s? false: true;
	}
	
	public void makeRegion(int position) throws ErrorException
	{
		sortRegion(m_blocks, 0, m_blocks.size() - 1, true);
		m_pages = new ArrayList<Integer>();
		for(int i = 0, size = m_blocks.size(); i < size; ++i)
		{
			RegionAble ra = m_blocks.get(i);
			if(ra.getWidth() > m_size || ra.getHeight() > m_size)
			{
				throw new ErrorException(ErrorDefine.E_ATLAS_IMAGE_TOO_LARGE_ERROR, position);
			}
			sortRegion(m_emptyRegions, 0, m_emptyRegions.size() - 1, false);
			int index = findRegion(ra.getWidth(), ra.getHeight());
			if(index >= 0)
			{
				Region r = (Region)m_emptyRegions.get(index);
				ra.setCoo(r);
				if(r.getWidth() > ra.getWidth())
				{
					Region newR = new Region();
					newR.m_left = r.m_left + ra.getWidth();
					newR.m_right = r.m_right;
					newR.m_top = r.m_top;
					newR.m_bottom = r.m_top + ra.getHeight();
					m_emptyRegions.add(newR);
				}
				if(r.getHeight() > ra.getHeight())
				{
					Region newR = new Region();
					newR.m_left = r.m_left;
					newR.m_right = r.m_right;
					newR.m_top = r.m_top + ra.getHeight();
					newR.m_bottom = r.m_bottom;
					m_emptyRegions.add(newR);
				}
				m_emptyRegions.remove(index);
			}
			else
			{
				m_pages.add(--i);
				resetRegion();
			}
		}
		m_pages.add(m_blocks.size() - 1);
	}
	
	public void draw(Color bgColor) throws ErrorException
	{
		int start = 0;
		for(int max: m_pages)
		{
			BufferedImage image = new BufferedImage(m_size, m_size, BufferedImage.TYPE_INT_ARGB);
			Graphics g = image.getGraphics();
			g.setColor(bgColor);
			g.fillRect(0, 0, m_size, m_size);
			for(; start <= max; ++start)
			{
				drawBlock(g, m_blocks.get(start));
			}
			m_images.add(image);
			start = max + 1;
		}
	}
	
	public void saveToFile(String path, int position) throws ErrorException
	{
		try
		{
			for(int i = 0, max = m_images.size(); i < max; ++i)
			{
				String str = path + i + ".png";
				ImageIO.write(m_images.get(i), "PNG", new File(str));
			}
		} 
		catch(Exception e) 
		{
			throw new ErrorException(ErrorDefine.E_CANT_SAVE_IMAGE_ERROR, position);
		}
	}
	
	public XmlNode getXmlRoot()
	{
		XmlNode root = new XmlNode(Config.FONT_ROOT);
		int start = 0;
		for(int max: m_pages)
		{
			XmlNode page = new XmlNode(Config.FONT_PAGE);
			for(; start <= max; ++start)
			{
				page.addChild(blockToXml(m_blocks.get(start)));
			}
			root.addChild(page);
		}
		return root;
	}
	
	protected void drawBlock(Graphics g, RegionAble block) throws ErrorException
	{
		ImageInfo info = (ImageInfo)block;
		try 
		{
			BufferedImage image = ImageIO.read(new File(info.m_path.toString()));
			g.drawImage(image, info.m_x, info.m_y, null);
		} 
		catch(Exception e)
		{
			throw new ErrorException(ErrorDefine.E_CANT_READ_IMAGE_ERROR, info.m_path.toString());
		}
	
	}
	
	protected XmlNode blockToXml(RegionAble block)
	{
		ImageInfo info = (ImageInfo)block;
		XmlNode node = new XmlNode(Config.ATLAS_IMAGE);
		node.addAttribute(Config.FONT_ATTR_X, info.m_x + "");
		node.addAttribute(Config.FONT_ATTR_Y, info.m_y + "");
		node.addAttribute(Config.FONT_ATTR_W, info.m_w + "");
		node.addAttribute(Config.FONT_ATTR_H, info.m_h + "");
		node.addAttribute(Config.FONT_ATTR_ID, info.m_name);
		return node;
	}
	
	public void output(String path, Color bgColor, int position) throws ErrorException
	{
		makeRegion(position);
		draw(bgColor);
		saveToFile(path, position);
		XmlNode root = getXmlRoot();
		XmlDoc doc = new XmlDoc(root);
		doc.saveToFile(path + ".xml");
	}
}
